
/* GCSx
** TREEVIEW.CPP
**
** Treeview window, for browsing worlds, libraries, etc.
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

int TreeView::iconX[2] = { 0, 0 };
int TreeView::iconY[2] = { 0, 0 };
int TreeView::iconWidth[2] = { 0, 0 };
int TreeView::iconHeight[2] = { 0, 0 };
int TreeView::iconIndent[2] = { 0, 0 };
int TreeView::itemHeight = 0;
int TreeView::itemYOffset = 0;
const string TreeView::wtButtonDown("n");
const string TreeView::wtButtonRight("k");
SDL_Surface* TreeView::iconSurface = NULL;
long int TreeView::iconsLoaded = 0;
const string TreeView::resourceError("Error loading resource/treeicon.bmp");

TreeView::TreeView(const string& name, void* tPtr, int tCode, int (*tAction)(void* ptr, int code, int command, int check), int hideTop, int caseSensitive) : item(name), sort(name), subtree() { start_func
    isCaseSensitive = caseSensitive;

    if (!isCaseSensitive) toLower(sort);

    if (!iconsLoaded) {
        iconWidth[0] = fontHeight(FONT_WIDGET);
        iconHeight[0] = iconWidth[0];
        iconIndent[0] = iconWidth[0] + GUI_TREEVIEW_ICON_PADDING * 2;
        iconWidth[1] = GUI_TREEVIEW_ICON_WIDTH;
        iconHeight[1] = GUI_TREEVIEW_ICON_HEIGHT;
        iconIndent[1] = iconWidth[1] + GUI_TREEVIEW_ICON_PADDING * 2;
        itemHeight = fontHeight();
        if (iconHeight[0] > itemHeight) itemHeight = iconHeight[0];
        if (iconHeight[1] > itemHeight) itemHeight = iconHeight[1];
        iconY[0] = (itemHeight - iconHeight[0]) / 2;
        iconX[0] = (iconIndent[0] - iconWidth[0]) / 2;
        iconY[1] = (itemHeight - iconHeight[1]) / 2;
        iconX[1] = iconIndent[0] + (iconIndent[1] - iconWidth[1]) / 2;
        iconIndent[1] += iconIndent[0];
        itemYOffset = (itemHeight - fontHeight()) / 2;
        
        string iconFile;
        createFilename(resourceDir->c_str(), "treeicon.bmp", iconFile);

        iconSurface = SDL_LoadBMP(iconFile.c_str());
        if (iconSurface == NULL) {
            guiErrorBox(resourceError, errorTitleResourceLoad);
        }
        else {
            SDL_SetColorKey(iconSurface, SDL_SRCCOLORKEY, SDL_MapRGB(iconSurface->format, 255, 0, 0));
        }
    }

    ptr = tPtr;
    code = tCode;
    action = tAction;

    hideItem = hideTop;    
    myFrame = NULL;
    myScroll = NULL;
    parentIsTree = 0;
    // (limit length of display but don't affect actual data)
    itemWidth = hideItem ? 0 : fontWidth(item.substr(0, MAX_LINELENGTH));
    subitems = 0;
    expanded = hideItem; // If item is hidden, we're always expanded
    currentSelection = -1;
    
    iconOpen = -1;
    iconClosed = -1;
    
    open = 0;
    setDirty();
    width = (hideItem ? 0 : iconIndent[1]) + itemWidth;
    height = (hideItem ? 0 : itemHeight);

    ++iconsLoaded;
}

TreeView::~TreeView() { start_func
    // We assume we've been removed from any parent listview or from the desktop
    vector<TreeView*>::iterator end = subtree.end();
    for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
        delete *pos;
    }
    
    if  (--iconsLoaded == 0) {
        if (iconSurface) SDL_FreeSurface(iconSurface);
        iconSurface = NULL;
    }
}

int TreeView::wantsToBeDeleted() const { start_func
    return 0;
}

Window::CommandSupport TreeView::supportsCommand(int cmdCode) const { start_func
    // If we're not selected, propogate command downward to selected first
    if (currentSelection > 0) {
        CommandSupport result = subtree[currentSelection - 1]->supportsCommand(cmdCode);
        if (result != COMMAND_HIDE) return result;
    }
    
    // Either we're selected, or our child refused it, so we'll try it now
    // Note that SDL_COMMAND is the only action that propogates down the tree
    if (action) {
        return (Window::CommandSupport)action(ptr, code, cmdCode, 1);
    }
    
    return COMMAND_HIDE;
}

int TreeView::event(int hasFocus, const SDL_Event* event) { start_func
    assert(event);

    int handleKey = 0;
    int repeat = 1;
    SDL_Event eventCopy;
    
    switch (event->type) {
        case SDL_CLOSE:
            open = 0;
            myFrame = NULL;
            myScroll = NULL;
            return 1;
            
        case SDL_COMMAND:
            // If we're not selected, propogate command downward to selected first
            if (currentSelection > 0) {
                if (subtree[currentSelection - 1]->event(hasFocus, event)) return 1;
            }
            
            // Either we're selected, or our child refused it, so we'll try it now
            // Note that SDL_COMMAND is the only action that propogates down the tree
            if (action) {
                return action(ptr, code, event->user.code, 0);
            }
            
            return 0;
    
        case SDL_KEYDOWN:
            // Handle top-level events here (we assume we're top level, as we
            //   don't allow these keys to propogate further)
            // Change PGDN / PGUP into repeated keypresses sent to ourselves
            // Handle END / HOME
            switch (combineKey(event->key.keysym.sym, event->key.keysym.mod)) {
                case SDLK_HOME:
                    if ((subitems) && (hideItem) && (expanded)) {
                        subtree[0]->selectMe(); // (start with top item selected)
                    }
                    else selectMe();
                    return 1;

                case SDLK_END:
                    selectLast();
                    return 1;

                case SDLK_PAGEDOWN:
                    eventCopy = *event;
                    eventCopy.key.keysym.sym = SDLK_DOWN;
                    repeat = viewHeight / fontHeight() - 1;
                    if (repeat < 1) repeat = 1;
                    for (; repeat > 0; --repeat) {
                        TreeView::event(hasFocus, &eventCopy);
                    }
                    return 1;

                case SDLK_PAGEUP:
                    eventCopy = *event;
                    eventCopy.key.keysym.sym = SDLK_UP;
                    repeat = viewHeight / fontHeight() - 1;
                    if (repeat < 1) repeat = 1;
                    for (; repeat > 0; --repeat) {
                        TreeView::event(hasFocus, &eventCopy);
                    }
                    return 1;
            }
        
            // If we're not selected, propogate key downward to selected
            if (currentSelection > 0) {
                if (subtree[currentSelection - 1]->event(hasFocus, event)) return 1;
                handleKey = 1;
            }
            
            // If we're selected, handle key
            // If the item actually selected didn't use the key, we'll try to
            if ((handleKey) || (currentSelection == 0)) {
                switch (combineKey(event->key.keysym.sym, event->key.keysym.mod)) {
                    case SDLK_KP_ENTER:
                    case SDLK_RETURN:
                    case SDLK_SPACE:
                        if (action) {
                            action(ptr, code, LV_SELECT, 0);
                        }
                        // We always return 1, we don't want lower level to intercept
                        return 1;

                    case SDLK_BACKSPACE:
                        // Go up a level
                        if (currentSelection > 0) {
                            if (!hideItem) selectMe();
                            return 1;
                        }
                        break;

                    case SDLK_DELETE:
                        if (action) {
                            action(ptr, code, LV_DELETE, 0);
                        }
                        // We always return 1, we don't want lower level to intercept
                        return 1;
                        
                    case SDLK_RIGHT:
                        // Expand or go inward a level
                        // We use function (not variable) to support directory view
                        // where items aren't loaded until expand() is called
                        if (hasSubItems()) {
                            if ((currentSelection == 0) && (expanded) && (subitems)) subtree[0]->selectMe();
                            else expand(1);
                        }
                        return 1;

                    case SDLK_LEFT:
                        // Collapse or go up a level
                        if (currentSelection > 0) {
                            if (!hideItem) selectMe();
                            return 1;
                        }
                        else if ((subitems) && (expanded)) {
                            expand(0);
                            return 1;
                        }
                        break;

                    case SDLK_DOWN:
                        // Next line
                        if ((currentSelection > 0) && (currentSelection < subitems)) {
                            // currentSelection - 1, plus one for next item
                            subtree[currentSelection]->selectMe();
                            return 1;
                        }
                        // First item of us if expanded
                        else if ((currentSelection == 0) && (expanded) && (subitems)) {
                            subtree[0]->selectMe();
                            return 1;
                        }
                        break;

                    case SDLK_UP:
                        // Previous line
                        if (currentSelection > 1) {
                            // currentSelection - 1, minus one for previous item
                            subtree[currentSelection - 2]->selectLast();
                            return 1;
                        }
                        // Select self
                        else if ((currentSelection == 1) && (!hideItem)) {
                            selectMe();
                            return 1;
                        }
                        break;
                        
                    default: {
                        // Jump to first/next matching item based on letter
                        if (selectFind(tolower(event->key.keysym.sym))) return 1;
                        break;
                    }
                }
            }
            
            break;
    
        case SDL_MOUSEBUTTONDBL:
        case SDL_MOUSEBUTTONDOWN:
            // (either button OK)
            if ((event->button.button == SDL_BUTTON_LEFT) || (event->button.button == SDL_BUTTON_RIGHT)) {
                if (event->button.y < height) {
                    // Ourselves?
                    if ((event->button.y < itemHeight) && (!hideItem)) {
                        // Button?
                        if (event->button.x < iconIndent[0]) {
                            expand(!expanded);
                        }
                        // Item?
                        else if (event->button.x < itemWidth + iconIndent[1]) {
                            selectMe();
                            
                            // Double-left-click?
                            if ((event->button.button == SDL_BUTTON_LEFT) && (event->type == SDL_MOUSEBUTTONDBL)) {
                                if (expanded) expand(0);
                                else if (hasSubItems()) expand(1);
                                else if (action) return action(ptr, code, LV_SELECT, 0);
                            }
                            else if ((action) && (event->type == SDL_MOUSEBUTTONDOWN)) {
                                if (event->button.button == SDL_BUTTON_LEFT) return action(ptr, code, LV_LCLICK, 0);
                                else if (event->button.button == SDL_BUTTON_RIGHT) return action(ptr, code, LV_RCLICK, 0);
                            }
                        }
                        else {
                            return 0;
                        }
                        return 1;
                    }
                    // A subitem
                    else if (expanded) {
                        // New event, move towards next item
                        eventCopy = *event;
                        if (!hideItem) {
                            eventCopy.button.x -= iconIndent[0];
                            eventCopy.button.y -= itemHeight;
                        }
                        
                        vector<TreeView*>::iterator end = subtree.end();
                        for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
                            // This item?
                            if (eventCopy.button.y < (*pos)->height) {
                                // Recurse
                                return (*pos)->event(hasFocus, &eventCopy);
                            }
                            
                            // Move towards next item
                            eventCopy.button.y -= (*pos)->height;
                        }

                        return 0;
                    }
                }
            }
            break;
    }

    return 0;
}

void TreeView::resolutionChange(int fromW, int fromH, int fromBpp, int toW, int toH, int toBpp) { start_func
    Window::resolutionChange(fromW, fromH, fromBpp, toW, toH, toBpp);
    vector<TreeView*>::iterator end = subtree.end();
    for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
        (*pos)->resolutionChange(fromW, fromH, fromBpp, toW, toH, toBpp);
    }
}

void TreeView::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
    assert(destSurface);

    if (visible) {
        // This will be the final displayed amount, we update it as we go but still work
        // off of the original dirtied area for efficiency
        Rect updatedArea = toDisplay;
        
        xOffset += x;
        yOffset += y;
    
        // This window attempts to only redraw lines that have changed (or dirty)
        // Our top member-
        if (!hideItem) {
            // Determine clip area for our item
            Rect itemClip = { xOffset, yOffset, width, itemHeight };
            intersectRects(itemClip, clipArea);

            // If dirty and visible, or intersects with dirty area
            if (((dirty) && (itemClip.w)) || (intersectRects(itemClip, toDisplay))) {
                // Add rect into updated area
                boundRects(updatedArea, itemClip);
                SDL_SetClipRect(destSurface, &itemClip);

                // Our background- just our item
                SDL_FillRect(destSurface, &itemClip, guiPacked[COLOR_TEXTBOX]);
        
                // Icon +/-
                if (hasSubItems()) {
                    drawGuiBox(xOffset + iconX[0], yOffset + iconY[0], iconWidth[0], iconHeight[0], 1, destSurface);
                    drawText(expanded ? wtButtonDown : wtButtonRight, guiRGB[COLOR_TEXT], xOffset + iconX[0], yOffset + iconY[0], destSurface, FONT_WIDGET);
                }
                
                // Icon symbol
                int iconSymbol = 0;
                if ((iconSurface) && (iconOpen >= 0)) {
                    iconSymbol = 1;
                    blit((expanded ? iconOpen : iconClosed) * GUI_TREEVIEW_ICON_WIDTH, 0, iconSurface, xOffset + iconX[1], yOffset + iconY[1], destSurface, 16, 16);
                }
    
                // Our text- selected?
                if (currentSelection == 0) {
                    drawGradient(xOffset + iconIndent[iconSymbol], yOffset + itemYOffset, itemWidth, itemHeight, guiRGB[COLOR_SELECTION1], guiRGB[COLOR_SELECTION2], destSurface);
                    drawText(item, guiRGB[COLOR_TEXTBOX], xOffset + iconIndent[iconSymbol], yOffset + itemYOffset, destSurface);
                    drawFocusBox(xOffset + iconIndent[iconSymbol], yOffset + itemYOffset, itemWidth, itemHeight, destSurface);
                }
                else {
                    drawText(item, guiRGB[COLOR_TEXT], xOffset + iconIndent[iconSymbol], yOffset + itemYOffset, destSurface);
                }
            }
        }

        // Subtree?
        if ((expanded) && (subitems) && ((childDirty) || (totalDirty) || (toDisplay.w))) {
            int atY = yOffset + (hideItem ? 0 : itemHeight);
            vector<TreeView*>::iterator end = subtree.end();
            for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
                // Determine clip area for this item
                Rect itemClip = { xOffset, atY, width, (*pos)->height };
                intersectRects(itemClip, clipArea);
                
                // Dirty area
                Rect itemDirty = itemClip;

                // If dirty and visible, or intersects with dirty area
                // Order of "if" evaluation matters here-
                // If total dirty, entire area will be redrawn; if just dirty,
                // only area clipped to "toDisplay" will be enforced as redrawn
                if ((itemDirty.w) && ((totalDirty) || (intersectRects(itemDirty, toDisplay)) || ((*pos)->isDirty()))) {
                    // Give it a full background too- it will draw over it anyways
                    // We have to do this because if item may not cover it's full width
                    SDL_SetClipRect(destSurface, &itemClip);
                    SDL_FillRect(destSurface, &itemDirty, guiPacked[COLOR_TEXTBOX]);
                    
                    // (don't use move, to prevent unneeded setdirties)
                    (*pos)->x = hideItem ? 0 : iconIndent[0];
                    (*pos)->y = atY - yOffset;
                    (*pos)->display(destSurface, itemDirty, itemClip, xOffset, yOffset);
                    
                    // Add into redrawn area
                    boundRects(updatedArea, itemDirty);
                }
                atY += (*pos)->height;
            }
        }
        
        toDisplay = updatedArea;
        dirty = totalDirty = childDirty = 0;
    }
}

Window::WindowType TreeView::windowType() const { start_func
    return WINDOW_CLIENT;
}

void TreeView::setIcon(int tIconClosed, int tIconOpen) { start_func
    iconClosed = tIconClosed;
    iconOpen = tIconOpen;
    if (iconOpen == -1) iconOpen = iconClosed;

    setDirty();
}

void TreeView::changeName(const string& name) { start_func
    item = name;
    sort = name;

    if (!isCaseSensitive) toLower(sort);

    setDirty();

    if ((parent) && (parentIsTree)) {
        dynamic_cast<TreeView*>(parent)->childRenamed(this);
    }
    
    if (myFrame) {
        myFrame->setTitle(item);
    }

    if (!hideItem) {
        // (limit length of display but don't affect actual data)
        itemWidth = fontWidth(item.substr(0, MAX_LINELENGTH));

        // Scan all items for widest (add in iconIndent when done)
        int widest = itemWidth;
        vector<TreeView*>::iterator end = subtree.end();
        for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
            if ((*pos)->width > widest) widest = (*pos)->width;
        }

        resize(widest + iconIndent[1], height);
    }
}

void TreeView::childRenamed(TreeView* child) { start_func
    assert(child);

    // Selected?
    int wasSelected = 0;
    if ((currentSelection > 0) && (subtree[currentSelection - 1] == child)) {
        wasSelected = 1;
        currentSelection = 0;
    }

    // Find existing and remove
    vector<TreeView*>::iterator pos = subtree.begin();
    vector<TreeView*>::iterator end = subtree.end();
    int count = 0;
    for (; pos != end; ++count, ++pos) {
        if (*pos == child) break;
    }
    assert(pos != end);
    if ((currentSelection > 0) && (count < currentSelection)) --currentSelection;
    subtree.erase(pos);
    
    // Insert
    pos = subtree.begin();
    end = subtree.end();
    count = 0;
    for (; pos != end; ++count, ++pos) {
        if ((*pos)->sort > child->sort) break;
    }
    if ((currentSelection > 0) && (count < currentSelection)) ++currentSelection;
    subtree.insert(pos, child);
    
    // Select?
    if (wasSelected) currentSelection = count + 1;

    // We're 100% dirty
    setDirty(1);
}

void TreeView::insert(TreeView* toInsert, int makeCurrent) { start_func
    assert(toInsert);
    
    // Insert
    vector<TreeView*>::iterator pos = subtree.begin();
    vector<TreeView*>::iterator end = subtree.end();
    int count = 0;
    int atY = hideItem ? 0 : itemHeight;
    for (; pos != end; ++count, ++pos) {
        if ((*pos)->sort > toInsert->sort) break;
        atY += (*pos)->height;
    }
    if ((currentSelection > 0) && (count < currentSelection)) ++currentSelection;
    subtree.insert(pos, toInsert);
    toInsert->setParent(this);
    toInsert->parentIsTree = 1;
    ++subitems;
    
    // We may resize if needed
    int newWidth = width;
    int newHeight = height;
    
    // Ensure our width is large enough to include this item
    if (toInsert->width + (hideItem ? 0 : iconIndent[1]) > width) {
        newWidth = toInsert->width + (hideItem ? 0 : iconIndent[1]);
    }
    
    // If we're expanded, add subitem's height to ours
    if (expanded) {
        newHeight += toInsert->height;
    }
    
    // Modify size
    if ((newWidth != width) || (newHeight != height)) {
        // (sets dirty)
        resize(newWidth, newHeight);        
    }

    toInsert->move(hideItem ? 0 : iconIndent[0], atY);
    // Set this item as selected?
    if (makeCurrent) toInsert->selectMe();
    // Otherwise, rescroll to view current selection
    else positionChildren(1);
}

void TreeView::childResized(int oldW, int oldH, int newW, int newH, Window* child) { start_func
    // We may resize if needed
    int newWidth = width;
    int newHeight = height;
    
    // Need to be wider?
    if ((newW > oldW) && (newW + (hideItem ? 0 : iconIndent[1]) > width)) {
        newWidth = newW + (hideItem ? 0 : iconIndent[1]);
    }
    
    // Possibly need to be thinner?
    if ((newW < oldW) && ((oldW + (hideItem ? 0 : iconIndent[1])) == width)) {
        // Scan all items for widest (add in iconIndent when done)
        int widest = itemWidth;
        vector<TreeView*>::iterator end = subtree.end();
        for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
            if ((*pos)->width > widest) widest = (*pos)->width;
        }
        newWidth = widest + (hideItem ? 0 : iconIndent[1]);
    }
    
    // If we're expanded, then height changes also affect us, and all
    // other children after the child that resized
    if (expanded) {
        int heightDiff = newH - oldH;
        if (heightDiff) {
            newHeight += heightDiff;
            
            int after = 0;
            vector<TreeView*>::iterator end = subtree.end();
            for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
                if (after) (*pos)->move((*pos)->x, (*pos)->y + heightDiff);
                if (*pos == child) after = 1;
            }
        }
    }
    
    // Resize?
    if ((newWidth != width) || (newHeight != height)) {
        // (sets dirty)
        resize(newWidth, newHeight);        
    }
}

TreeView* TreeView::findRecursive(void* fPtr) { start_func
    vector<TreeView*>::iterator end = subtree.end();
    for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
        TreeView* result = (*pos)->findRecursive(fPtr);
        if (result) return result;
        if ((*pos)->ptr == fPtr) return *pos;
    }    
    return NULL;
}

TreeView* TreeView::find(void* fPtr) { start_func
    vector<TreeView*>::iterator end = subtree.end();
    for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
        if ((*pos)->ptr == fPtr) return *pos;
    }    
    return NULL;
}

TreeView* TreeView::findRecursive(int fCode, int (*fAction)(void* ptr, int code, int command, int check)) { start_func
    vector<TreeView*>::iterator end = subtree.end();
    for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
        TreeView* result = (*pos)->findRecursive(fCode, fAction);
        if (result) return result;
        if (((*pos)->code == fCode) &&
            (((*pos)->action == fAction) || (fAction == NULL))) return *pos;
    }
    return NULL;
}

TreeView* TreeView::find(const string& fItem) { start_func
    vector<TreeView*>::iterator end = subtree.end();

    if (!isCaseSensitive) {
        string lowercase = fItem;
        for (int pos = lowercase.size() - 1; pos >= 0; --pos) {
            lowercase[pos] = tolower(lowercase[pos]);
        }
        for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
            if ((*pos)->sort == lowercase) return *pos;
        }    
    }
    else {
        for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
            if ((*pos)->item == fItem) return *pos;
        }    
    }
    
    return NULL;
}

TreeView* TreeView::findParent() { start_func
    if ((parent) && (parentIsTree)) {
        return dynamic_cast<TreeView*>(parent);
    }
    
    return NULL;
}

TreeView* TreeView::findSelected() { start_func
    if (currentSelection > 0) return subtree[currentSelection - 1]->findSelected();
    if (currentSelection == 0) return this;
    return NULL;
}

void TreeView::removeAll() { start_func
    expand(0);

    // Select self instead of any child
    if (currentSelection > 0) {
        selectMe();
    }

    vector<TreeView*>::iterator end = subtree.end();
    for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
        (*pos)->parentIsTree = 0;
        (*pos)->setParent(NULL);
        delete (*pos);
    }
    
    subtree.clear();
    subitems = 0;

    // We may resize if needed
    int newWidth = itemWidth + (hideItem ? 0 : iconIndent[1]);
    int newHeight = (hideItem ? 0 : itemHeight);

    if ((newWidth != width) || (newHeight != height)) {
        // (sets dirty)
        resize(newWidth, newHeight);        
    }
}

void TreeView::remove(TreeView* toRemove, int deleteIt) { start_func
    // Track if we passed selected item
    int sawSelected = 0;

    vector<TreeView*>::iterator end = subtree.end();
    for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
        if (*pos == toRemove) {
            // If was selected, select self instead
            if ((currentSelection > 0) && (subtree[currentSelection - 1] == toRemove)) {
                sawSelected = 1;
                selectMe();
            }

            subtree.erase(pos);
            // Must use toRemove from here onward
            toRemove->parentIsTree = 0;
            toRemove->setParent(NULL);
            --subitems;

            childResized(toRemove->width, toRemove->height, 0, 0, toRemove);

            if (deleteIt) delete toRemove;
            
            // Decrease selected pointer because we removed above it?
            if ((currentSelection > 0) && (!sawSelected)) --currentSelection;
            break;
        }
        else if ((currentSelection > 0) && (subtree[currentSelection - 1] == *pos)) {
            sawSelected = 1;
        }
    }    
}

void TreeView::expand(int state) { start_func
    int newHeight = height;
    
    // Can't change if top item hidden- always expanded
    if (hideItem) return;

    // Expanding?
    if ((state) && (!expanded)) {
        expanded = 1;
        
        // Add height of all items
        vector<TreeView*>::iterator end = subtree.end();
        for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
            newHeight += (*pos)->height;
        }
    }
    // Contracting?
    else if ((!state) && (expanded)) {
        expanded = 0;
        newHeight = itemHeight;
    }
        
    // Size change?
    if (newHeight != height) {
        // (sets dirty)
        resize(width, newHeight);        
    }
    
    // Select ourselves if a subitem of us is selected and we just contracted
    if ((!expanded) && (currentSelection > 0)) selectMe();
}

void TreeView::addTo(Dialog* dialog, int showWidth, int showLines, int cId) { start_func
    assert(!myFrame);
    assert(dialog);

    // Set to standard size
    if (showWidth <= 0) {
        string standard(GUI_TREEVIEW_DEFAULTWIDTH, 'X');
        showWidth = fontWidth(standard);
    }

    if (showLines <= 0) {
        showLines = GUI_TREEVIEW_DEFAULTLINES;
    }

    expand(1); // (start with top level expanded)
    if (hasSubItems()) {
        subtree[0]->selectMe(); // (start with top item selected)
    }
    else {
        selectMe(); // (start with self selected)
    }

    myScroll = new WidgetScroll(WidgetScroll::FRAMETYPE_BEVEL, this, showWidth, showLines * itemHeight, cId);
    dialog->addWidget(myScroll);
}

void TreeView::runWindowed() { start_func
    assert(!myScroll);

    // Prevent duplication
    if (myFrame) {
        desktop->bringToTop(myFrame);
        return;
    }

    expand(1); // (start with top level expanded)
    if (hasSubItems()) {
        subtree[0]->selectMe(); // (start with top item selected)
    }
    else {
        selectMe(); // (start with self selected)
    }

    // We remember the frame pointer even though it'll delete itself
    myFrame = new FrameWindow(item, FrameWindow::RESIZING_NORMAL, FrameWindow::FRAMETYPE_BEVEL_TEXT, this);
    open = 1;
    
    // Cascade, use cascade height, but use OUR width
    myFrame->show(FrameWindow::SHOW_CASCADE, FrameWindow::SHOW_CASCADE, FrameWindow::SHOW_CURRMIN, FrameWindow::SHOW_CASCADE);
}

void TreeView::scrollToView(int sX, int sY, int sWidth, int sHeight) { start_func
    if (parent) {
        if (parentIsTree) {
            dynamic_cast<TreeView*>(parent)->positionChildren(0);
            dynamic_cast<TreeView*>(parent)->scrollToView(sX + x, sY + y, sWidth, sHeight);
        }
        else if (parent == myFrame) {
            myFrame->scrollToView(sX, sY, sWidth, sHeight);
        }
        else if (parent == myScroll) {
            myScroll->scrollToView(sX, sY, sWidth, sHeight);
        }
    }
}

int TreeView::selectFind(int letter) { start_func
    if (expanded) {
        int pos = currentSelection - 1;
        if (pos < 0) pos = 0;
        for (; pos < subitems; ++pos) {
            // (doesn't match root of currently selected item
            if ((tolower(subtree[pos]->item[0]) == letter) && (pos >= currentSelection)) {
                subtree[pos]->selectMe();
                return 1;
            }
            // (search subtree)
            if (subtree[pos]->selectFind(letter)) return 1;
        }
    }
    return 0;
}

void TreeView::selectLast() { start_func
    if ((expanded) && (subitems)) subtree[subitems - 1]->selectLast();
    else selectMe();
}

void TreeView::selectFirst() { start_func
    if ((expanded) && (subitems)) subtree[0]->selectMe();
    else selectMe();
}

void TreeView::selectMe() { start_func
    // Already selected?
    if (currentSelection == 0) return;

    // Subitem currently selected?
    if (currentSelection > 0) {
        subtree[currentSelection - 1]->unselectMe();
    }
    
    // Scroll to view it
    currentSelection = 0;
    scrollToView(0, 0, iconIndent[1] + itemWidth, itemHeight);
    
    // Tell parent, if any, that we're selected
    if ((parent) && (parentIsTree)) {
        dynamic_cast<TreeView*>(parent)->selectChild(this);
    }
    
    // Event
    if (action) action(ptr, code, LV_MOVE, 0);

    setDirty();
}

void TreeView::positionChildren(int rescroll) { start_func
    if (subitems) {
        int atY = hideItem ? 0 : itemHeight;
        vector<TreeView*>::iterator end = subtree.end();
        for (vector<TreeView*>::iterator pos = subtree.begin(); pos != end; ++pos) {
            // (don't use move, to prevent unneeded setdirties)
            (*pos)->x = hideItem ? 0 : iconIndent[0];
            (*pos)->y = atY;
            if (expanded) atY += (*pos)->height;
        }
    }
    if (rescroll) {
        if (currentSelection == 0) scrollToView(0, 0, iconIndent[1] + itemWidth, itemHeight);
        else if (currentSelection > 0) scrollToView(subtree[currentSelection - 1]->x, subtree[currentSelection - 1]->y,
                                                    subtree[currentSelection - 1]->itemWidth,
                                                    subtree[currentSelection - 1]->itemHeight);
    }
}

void TreeView::selectChild(TreeView* child) { start_func
    // Find child
    int cPos = findChild(child);
    
    // Found?
    if (cPos >= 0) {
        // Already selected?
        if (currentSelection == cPos + 1) return;
    
        // Subitem currently selected?
        if (currentSelection > 0) {
            subtree[currentSelection - 1]->unselectMe();
        }
        else if (currentSelection == 0) {
            // We're dirty if we WERE selected
            setDirty();
        }

        currentSelection = cPos + 1;
        
        // Tell parent, if any, that we're selected
        if ((parent) && (parentIsTree)) {
            dynamic_cast<TreeView*>(parent)->selectChild(this);
        }
    }
}

void TreeView::unselectMe() { start_func
    // Already unselected?
    if (currentSelection == -1) return;

    // Subitem currently selected?
    if (currentSelection > 0) {
        subtree[currentSelection - 1]->unselectMe();
    }
    else {
        // We were selected, we're now dirty
        setDirty();
    }
    
    currentSelection = -1;
}

int TreeView::findChild(const TreeView* child) const { start_func
    for (int pos = 0; pos < subitems; ++pos) {
        if (subtree[pos] == child) return pos;
    }
    return -1;
}

int TreeView::hasSubItems() const { start_func
    return subitems > 0;
}

